#include <stdio.h>
#include <windows.h>
#include <mmsystem.h>

typedef struct _NativeMidiSong NativeMidiSong;
typedef unsigned short Uint16;
typedef unsigned int Uint32;
typedef unsigned char Uint8;

/* Midi Status Bytes */
#define MIDI_STATUS_NOTE_OFF	0x8
#define MIDI_STATUS_NOTE_ON	0x9
#define MIDI_STATUS_AFTERTOUCH	0xA
#define MIDI_STATUS_CONTROLLER	0xB
#define MIDI_STATUS_PROG_CHANGE	0xC
#define MIDI_STATUS_PRESSURE	0xD
#define MIDI_STATUS_PITCH_WHEEL	0xE
#define MIDI_STATUS_SYSEX	0xF

/* We store the midi events in a linked list; this way it is
   easy to shuffle the tracks together later on; and we are
   flexible in the size of each elemnt.
 */
typedef struct MIDIEvent
{
	Uint32	time;		/* Time at which this midi events occurs */
	Uint8	status;		/* Status byte */
	Uint8	data[2];	/* 1 or 2 bytes additional data for most events */

	Uint32	extraLen;	/* For some SysEx events, we need additional storage */
	Uint8	*extraData;

	struct MIDIEvent *next;
} MIDIEvent;

/* A single midi track as read from the midi file */
typedef struct
{
	Uint8 *data;					/* MIDI message stream */
	int len;						/* length of the track data */
} MIDITrack;

/* A midi file, stripped down to the absolute minimum - divison & track data */
typedef struct
{
	int division;					/* number of pulses per quarter note (ppqn) */
    int nTracks;                    /* number of tracks */
	MIDITrack *track;               /* tracks */
} MIDIFile;

struct _NativeMidiSong {
  int MusicLoaded;
  int MusicPlaying;
  MIDIHDR MidiStreamHdr;
  MIDIEVENT *NewEvents;
  Uint16 ppqn;
  int Size;
  int NewPos;
};

static int ReadMIDIFile(MIDIFile *mididata, FILE *rw)
{
	int i = 0;
	Uint32	ID;
	Uint32	size;
	Uint16	format;
	Uint16	tracks;
	Uint16	division;

	if (!mididata)
		return 0;
	if (!rw)
		return 0;

	/* Make sure this is really a MIDI file */
	fread(&ID, 1, 4, rw);
	if (ID != 'MThd')
		return 0;

	/* Header size must be 6 */
	fread(&size, 1, 4, rw);
	//size = BE_LONG(size);
	if (size != 6)
		return 0;

	/* We only support format 0 and 1, but not 2 */
	fread(&format, 1, 2, rw);
	format = (short int)format;
	if (format != 0 && format != 1)
		return 0;

	fread(&tracks, 1, 2, rw);
	//tracks = BE_SHORT(tracks);
	mididata->nTracks = tracks;

    /* Allocate tracks */
    mididata->track = (MIDITrack*) calloc(1, sizeof(MIDITrack) * mididata->nTracks);
    if (NULL == mididata->track)
    {
        printf("Out of memory\n");
        goto bail;
    }

	/* Retrieve the PPQN value, needed for playback */
	fread(&division, 1, 2, rw);
	mididata->division = (short int)division;


	for (i=0; i<tracks; i++)
	{
		fread(&ID, 1, 4, rw);	/* We might want to verify this is MTrk... */
		fread(&size, 1, 4, rw);
		//size = BE_LONG(size);
		mididata->track[i].len = size;
		mididata->track[i].data = malloc(size);
		if (NULL == mididata->track[i].data)
		{
			printf("Out of memory\n");
			goto bail;
		}
		fread(mididata->track[i].data, 1, size, rw);
	}
	return 1;

bail:
	for(;i >= 0; i--)
	{
		if (mididata->track[i].data)
			free(mididata->track[i].data);
	}

	return 0;
}

/*
 *  Convert a midi song, consisting of up to 32 tracks, to a list of MIDIEvents.
 *  To do so, first convert the tracks seperatly, then interweave the resulting
 *  MIDIEvent-Lists to one big list.
 */
static MIDIEvent *MIDItoStream(MIDIFile *mididata)
{
	MIDIEvent **track;
	MIDIEvent *head = CreateEvent(0,0,0,0);	/* dummy event to make handling the list easier */
	MIDIEvent *currentEvent = head;
	int trackID;

    if (NULL == head)
		return NULL;

    track = (MIDIEvent**) calloc(1, sizeof(MIDIEvent*) * mididata->nTracks);
	if (NULL == head)
        return NULL;

	/* First, convert all tracks to MIDIEvent lists */
	for (trackID = 0; trackID < mididata->nTracks; trackID++)
		track[trackID] = MIDITracktoStream(&mididata->track[trackID]);

	/* Now, merge the lists. */
	/* TODO */
	while(1)
	{
		Uint32 lowestTime = (Uint32)-1;
		int currentTrackID = -1;

		/* Find the next event */
		for (trackID = 0; trackID < mididata->nTracks; trackID++)
		{
			if (track[trackID] && (track[trackID]->time < lowestTime))
			{
				currentTrackID = trackID;
				lowestTime = track[currentTrackID]->time;
			}
		}

		/* Check if we processes all events */
		if (currentTrackID == -1)
			break;

		currentEvent->next = track[currentTrackID];
		track[currentTrackID] = track[currentTrackID]->next;

		currentEvent = currentEvent->next;


		lowestTime = 0;
	}

	/* Make sure the list is properly terminated */
	currentEvent->next = 0;

	currentEvent = head->next;
    free(track);
	free(head);	/* release the dummy head event */
	return currentEvent;
}

MIDIEvent *CreateMIDIEventList(FILE *rw, Uint16 *division)
{
	MIDIFile *mididata = NULL;
	MIDIEvent *eventList;
	int trackID;

	mididata = calloc(1, sizeof(MIDIFile));
	if (!mididata)
		return NULL;

	/* Open the file */
	if ( rw != NULL )
	{
		/* Read in the data */
		if ( ! ReadMIDIFile(mididata, rw))
		{
			free(mididata);
			return NULL;
		}
	}
	else
	{
		free(mididata);
		return NULL;
	}

	if (division)
		*division = mididata->division;

	eventList = MIDItoStream(mididata);

	for(trackID = 0; trackID < mididata->nTracks; trackID++)
	{
		if (mididata->track[trackID].data)
			free(mididata->track[trackID].data);
	}
	free(mididata->track);
    free(mididata);

	return eventList;
}

NativeMidiSong *native_midi_loadsong(const char *midifile)
{
	NativeMidiSong *newsong;
	MIDIEvent		*evntlist = NULL;
	FILE *rw;

	newsong=malloc(sizeof(NativeMidiSong));
	if (!newsong)
		return NULL;
	memset(newsong,0,sizeof(NativeMidiSong));

	/* Attempt to load the midi file */
	rw = fopen(midifile, "rb");
	if (rw) {
		evntlist = CreateMIDIEventList(rw, &newsong->ppqn);
		fclose(rw);
		if (!evntlist)
		{
			free(newsong);
			return NULL;
		}
	}

	MIDItoStream(newsong, evntlist);

	FreeMIDIEventList(evntlist);

	return newsong;
}
